package com.ferry.jraft.example;

import com.alipay.remoting.exception.RemotingException;
import com.ferry.jraft.Node;
import com.ferry.jraft.impl.NodeImpl;
import com.ferry.jraft.model.Peer;
import com.ferry.jraft.model.PeerGroup;
import com.ferry.jraft.model.dto.ClientRequest;
import com.ferry.jraft.model.dto.ClientResponse;
import com.ferry.jraft.model.dto.Request;
import com.ferry.jraft.rpc.RaftRpcClient;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author ferry
 * @create 2022/5/25 22:36
 * @description 栈计算器
 */
@Slf4j
public class StackClient {

    public List<Peer> peers = new ArrayList<>();
    public List<Node> nodes = new ArrayList<>();
    public int id = 1;

    private RaftRpcClient rpcClient;

    public StackClient() {
        rpcClient = new RaftRpcClient();
        try {
            rpcClient.init();
        } catch (Throwable e) {
            log.error("RaftRpcClient init failed");
        }
    }

    /**
     * 创建一个栈计算器实例（即分布式系统中的一个节点）
     *
     * @return 节点id
     */
    public int create() {
        PeerGroup group = new PeerGroup(peers);
        int port = 8081 + id;
        Peer peer = new Peer(id++, "localhost:" + port);
        peers.add(peer);
        Node node = new NodeImpl();
        node.loadConfig(peer, group);
        node.loadStateMachine(new StackStateMachine());
        try {
            node.init();
        } catch (Throwable e) {
            log.error("Node {}: Create failed", id);
        }
        nodes.add(node);
        return id-1;
    }

    /**
     * 删除对应id的节点
     *
     * @param id
     * @return 是否成功
     */
    public void delete(int id) {
        Node node = nodes.get(id - 1);
        try {
            node.destroy();
        } catch (Throwable e) {
            log.error("Node {}: Destroy failed", id);
        }
    }

    /**
     * 向对应节点的栈状态机中压入val
     *
     * @param id
     * @param val
     * @return 是否成功
     */
    public boolean push(int id, int val) {
        String res = getRes(id, "push " + val);
        if (res == null) {
            return false;
        }
        if ("ok".equals(res)) {
            return true;
        }
        return false;
    }

    /**
     * 对应节点的栈状态机弹栈，并返回该值
     *
     * @param id
     * @return
     */
    public Integer pop(int id) {
        String res = getRes(id, "pop ");
        if (res == null) {
            return null;
        }
        return Integer.parseInt(res);
    }

    /**
     * 将对应节点的栈状态机的两个栈顶元素出栈并作为操作数相加，结果再次入栈
     *
     * @param id
     * @return 0：成功，1：失败
     */
    public int add(int id) {
        String res = getRes(id, "add ");
        if (res == null) {
            return 1;
        }
        if ("ok".equals(res)) {
            return 0;
        }
        return 1;
    }

    /**
     * 将对应节点的栈状态机的两个栈顶元素出栈并作为操作数相减，结果再次入栈
     *
     * @param id
     * @return 0：成功，1：失败
     */
    public int sub(int id) {
        String res = getRes(id, "sub ");
        if (res == null) {
            return 1;
        }
        if ("ok".equals(res)) {
            return 0;
        }
        return 1;
    }

    /**
     * 将对应节点的栈状态机的两个栈顶元素出栈并作为操作数相乘，结果再次入栈
     *
     * @param id
     * @return 0：成功，1：失败
     */
    public int mul(int id) {
        String res = getRes(id, "mul ");
        if (res == null) {
            return 1;
        }
        if ("ok".equals(res)) {
            return 0;
        }
        return 1;
    }

    /**
     * 将对应节点的栈状态机的两个栈顶元素出栈并作为操作数相除，结果再次入栈
     *
     * @param id
     * @return 0：成功，1：失败
     */
    public int div(int id) {
        String res = getRes(id, "div ");
        if (res == null) {
            return 1;
        }
        if ("ok".equals(res)) {
            return 0;
        }
        return 1;
    }

    /**
     * 将对应节点的栈顶整数弹栈，自增后再次入栈
     *
     * @param id
     * @return
     */
    public int inc(int id) {
        String res = getRes(id, "inc ");
        if (res == null) {
            return 1;
        }
        if ("ok".equals(res)) {
            return 0;
        }
        return 1;
    }

    /**
     * 将对应节点的栈顶整数弹栈，自增后再次入栈
     *
     * @param id
     * @return
     */
    public int dec(int id) {
        String res = getRes(id, "dec ");
        if (res == null) {
            return 1;
        }
        if ("ok".equals(res)) {
            return 0;
        }
        return 1;
    }

    /**
     * 返回栈顶元素
     *
     * @param id
     * @return
     */
    public Integer get(int id) {
        String res = getRes(id, "get ");
        if (res == null) {
            return null;
        }
        return Integer.parseInt(res);
    }

    /**
     * 通过RPC向节点发送请求
     *
     * @param id  节点的id
     * @param val 请求的内容
     * @return
     */
    private ClientResponse sendToNode(int id, String val) {
        Node node = nodes.get(id - 1);
        Request request = new ClientRequest(val);
        request.setCmd(Request.CLIENT_REQUEST);
        String url = "localhost:" + (8081 + id);
        try {
            ClientResponse response = (ClientResponse) rpcClient.send(request, url, 2000);
            return response;
        } catch (RemotingException e) {
            log.error("Send to node failed");
        }
        return null;
    }

    /**
     * 通用结果处理，对于失败调用直接返回null
     *
     * @param id
     * @param val
     * @return
     */
    private String getRes(int id, String val) {
        ClientResponse clientResponse = sendToNode(id, val);
        if (clientResponse == null || clientResponse.getRes() == null || !clientResponse.isSuccess()) {
            return null;
        }
        String res = (String) clientResponse.getRes();
        if ("fail".equals(res)) {
            return null;
        }
        return res;
    }

}
